package com.starblink.imgsearch.widget

import android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.graphics.drawable.PaintDrawable
import android.os.Build
import android.util.*
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.view.animation.AccelerateDecelerateInterpolator
import android.view.animation.DecelerateInterpolator
import android.widget.FrameLayout
import android.widget.ImageView
import androidx.core.animation.addListener
import androidx.core.graphics.ColorUtils
import com.starblink.android.basic.ext.color
import com.starblink.android.basic.ext.visibleOrGone
import com.starblink.android.basic.extension.dp2px
import com.starblink.android.basic.sensorsdata.extension.trackData
import com.starblink.android.basic.sensorsdata.spm.cfg.SpmElementDef
import com.starblink.basic.ext.*
import com.starblink.imgsearch.R
import com.starblink.imgsearch.ext.checkSizeAndUpdate


class ThingsContainerView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    themeId: Int = -1
) : FrameLayout(context, attrs, themeId) {

    /**
     * 物体热点点击
     */
    var onHotPotClick: ((position: Int, rect: RectF) -> Unit)? = null


    var tingsRectF: List<RectF>? = null
        set(value) {
            field = value
            updateThingsHotSpots()
        }

    var curRectF: RectF? = null
        set(value) {
            field = value
            frameView.curRectF = value
            visibleImgs()
        }


    var imgSize: Size? = null
        set(value) {
            field = value
            frameView.imgSize = value
        }


    var onDraggedPosition: ((RectF) -> Unit)? = null
        set(value) {
            field = value
            frameView.onDraggedPosition = value
        }

    var trackPage : Int? = null

    private val frameView: ThingFrameView = ThingFrameView(context)


    private val imgs = arrayListOf<FrameLayout>()

    private val animSize = 24.dp2px()

    private val animRect = Rect(0, 0, animSize, animSize)

    private val animDrawables = arrayListOf<PaintDrawable>()

    private var thingAnim: ValueAnimator? = null


    init {
        addView(
            frameView,
            LayoutParams(
                LayoutParams.MATCH_PARENT,
                LayoutParams.MATCH_PARENT
            )
        )
    }

    private fun updateThingsHotSpots() {
        val size = 30.dp2px()

        val scale = if ((imgSize?.width
                ?: 0) > 0
        ) screenWidth.toFloat() / imgSize!!.width else 1f
        tingsRectF?.run {
            forEachIndexed { index, rect ->
                val parentLayout = FrameLayout(context).apply {
                    visibleOrGone(rect != curRectF)
                    setOnClickListener {
                        onHotPotClick?.invoke(index, rect)
                        curRectF = rect
                        visibleImgs()
                    }
                }
                if (trackPage != null) {
                    parentLayout.trackData(
                        element = SpmElementDef.Element_33055,
                        page = trackPage!!,
                    )
                }
                parentLayout.addView(ImageView(context).apply {
                    scaleType = ImageView.ScaleType.CENTER_INSIDE
                    val animDrawable = createDrawable()
                    animDrawables.add(animDrawable)
                    setImageDrawable(animDrawable)
                }, LayoutParams(animSize, animSize).apply {
                    gravity = Gravity.CENTER
                })
                parentLayout.addView(ImageView(context).apply {
                    scaleType = ImageView.ScaleType.CENTER_INSIDE
                    setImageResource(R.drawable.sp_circle_ting)
                }, LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
                    gravity = Gravity.CENTER
                })
                imgs.add(parentLayout)
                addView(parentLayout, LayoutParams(size, size).apply {
                    leftMargin = ((rect.left + rect.width() / 2) * scale - size / 2).toInt()
                    topMargin = ((rect.top + rect.height() / 2) * scale - size / 2).toInt()
                })
            }
        }

        if (thingAnim == null) {
            thingAnim = ValueAnimator.ofFloat(0.3f, 1F).apply {
                duration = 2000
                repeatCount = ValueAnimator.INFINITE
                repeatMode = ValueAnimator.RESTART
                this.interpolator = DecelerateInterpolator()

                addUpdateListener {
                    (it.animatedValue as Float?)?.let { value ->
                        animRect.right = (100 * value).toInt()
                        animRect.bottom = animRect.right
                        animDrawables.forEach {
                            it.alpha = 255 - (255 * value).toInt()
                            it.invalidateSelf()
                        }
                    }
                }
                addListener(onEnd = {

                })
            }
            thingAnim?.start()
        }

    }

    private fun createDrawable() = object : PaintDrawable(color(com.starblink.basic.style.R.color.sk_gray0)){
        override fun getIntrinsicWidth(): Int = animRect.width()
        override fun getIntrinsicHeight(): Int = animRect.height()
    }.apply {
        setCornerRadius(1000f)
    }

    override fun onDetachedFromWindow() {
        thingAnim?.cancel()
        super.onDetachedFromWindow()
    }

    private fun visibleImgs() {
        imgs.forEachIndexed { index2, imageView ->
            imageView.visibleOrGone(curRectF != tingsRectF?.get(index2))
        }
    }


}


/**
 * 图搜物体拖动View
 */
class ThingFrameView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    themeId: Int = -1
) : View(context, attrs, themeId) {


    var imgSize: Size? = null

    var curRectF: RectF? = null
        set(value) {
            // 图片上位置，需要对应view尺寸缩放
            value?.let {
                compareRectF.set(it)
                val scale =
                    if ((imgSize?.width ?: 0) > 0) viewRect.width() / imgSize!!.width else 1f
                compareRectF.scale(scale)
                compareRectF.checkSizeAndUpdate(viewRect)

                val newRect = drawRectF != compareRectF
                if (newRect) {
                    drawRectF.set(compareRectF)
                }

                if (newRect || !drawRectF.hasValue) {
                    if (oRect.hasValue.not()) {
                        post {
                            anim2Rect()
                        }
                    } else {
                        anim2Rect()
                    }
                }

//                if (oRect.hasValue) {
//                    anim2Rect()
//                } else {
//                    postInvalidate()
//                    oRect.set(drawRectF)
//                }
            }
            field = value
        }

    var onDraggedPosition: ((RectF) -> Unit)? = null

    private val viewRect = RectF()

    private val viewRadius = 0F

    private val paint = Paint().apply {
        isAntiAlias = true
        color = Color.argb((0.4 * 255 + 0.5f).toInt(), 15, 23, 42)
    }

    private val borderWidth = 4.dp2px()

    private val grayLineGap = 8.dp2px()

    private val centerGapRatio = 0.2f

    private val borderPath = Path()

    private val borderPath2 = Path()

    private val borderPaint = Paint().apply {
        isAntiAlias = true
        color = Color.WHITE
        strokeWidth = borderWidth.toFloat()
        style = Paint.Style.STROKE
        strokeCap = Paint.Cap.ROUND
    }

//    private val borderPaintCircle = Paint().apply {
//        isAntiAlias = true
//        color = Color.WHITE
//        style = Paint.Style.FILL
//    }

    private val borderPaint2 = Paint().apply {
        isAntiAlias = true
        color = ColorUtils.setAlphaComponent(Color.WHITE, 0x40)
        strokeWidth = borderWidth.toFloat()
        style = Paint.Style.STROKE
        // alpha = 64
        xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP)
        pathEffect = CornerPathEffect(borderWidth.toFloat())
        strokeCap = Paint.Cap.ROUND
    }
    
    private val oRect = RectF()

    private val drawRectF = RectF()

    private val compareRectF = RectF()

    private val bordLength = 12.dp2px()

    private val cornerRadius = 24.dp2px()

    private val clipPath = Path()

    private val cubicOffset = 0f //cornerRadius.shr(cornerRadius)

    /**
     * 四个角落手势偏差
     */
    private val pressInDis = 20.dp2px().toFloat()

    /**
     * 可拖动缩小最小框尺寸
     */
    private val minSize = SizeF(60.dp2px().toFloat(), 60.dp2px().toFloat())


    private var moveX = Float.MIN_VALUE
    private var moveY = Float.MIN_VALUE

    private var drawMoved = false

    /**
     * 左上，右上，左下，右下，是否在downz中
     */
    private val downPositionTag = booleanArrayOf(false, false, false, false)

    private fun MotionEvent.isEventTopLeft(): Boolean =
        this.x > drawRectF.left - pressInDis && this.y > drawRectF.top - pressInDis
                && this.x < drawRectF.left + pressInDis && this.y < drawRectF.top + pressInDis

    private fun MotionEvent.isEventTopRight() =
        this.x > drawRectF.right - pressInDis && this.y > drawRectF.top - pressInDis
                && this.x < drawRectF.right + pressInDis && this.y < drawRectF.top + pressInDis


    private fun MotionEvent.isEventBottomLeft() =
        this.x > drawRectF.left - pressInDis && this.y > drawRectF.bottom - pressInDis
                && this.x < drawRectF.left + pressInDis && this.y < drawRectF.bottom + pressInDis


    private fun MotionEvent.isEventBottomRight() =
        this.x > drawRectF.right - pressInDis && this.y > drawRectF.bottom - pressInDis
                && this.x < drawRectF.right + pressInDis && this.y < drawRectF.bottom + pressInDis

    private fun updateDownPosition(event: MotionEvent) {
        when {
            event.isEventTopLeft() -> {
                downPositionTag[0] = true
            }
            event.isEventTopRight() -> {
                downPositionTag[1] = true
            }
            event.isEventBottomLeft() -> {
                downPositionTag[2] = true
            }
            event.isEventBottomRight() -> {
                downPositionTag[3] = true
            }
        }
    }

    private fun refreshDrawRect(
        left: Float? = null,
        top: Float? = null,
        right: Float? = null,
        bottom: Float? = null
    ): Boolean {
        var refresh = false
        if (left != null && left > viewRect.left && (drawRectF.right - left >= minSize.width || left < drawRectF.left)) {
            drawRectF.left = left
            refresh = true
        }
        if (top != null && top > viewRect.top && (drawRectF.bottom - top >= minSize.height || top < drawRectF.top)) {
            drawRectF.top = top
            refresh = true
        }
        if (right != null && right < viewRect.right && (right - drawRectF.left >= minSize.width || right > drawRectF.right)) {
            drawRectF.right = right
            refresh = true
        }
        if (bottom != null && bottom < viewRect.bottom && (bottom - drawRectF.top >= minSize.height || bottom > drawRectF.bottom)) {
            drawRectF.bottom = bottom
            refresh = true
        }
        return refresh
    }


    override fun onTouchEvent(event: MotionEvent): Boolean {
        Log.d(
            TAG,
            "onTouchEvent: event ${MotionEvent.actionToString(event.action)}, x: ${event.x}, y: ${event.y}"
        )

        when (event.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                if (event.inRect(drawRectF, borderOffset = pressInDis)) {
                    moveX = event.x
                    moveY = event.y
                    updateDownPosition(event)
                }
                compareRectF.set(drawRectF)
            }
            MotionEvent.ACTION_MOVE, MotionEvent.ACTION_OUTSIDE -> {
                if (moveX != Float.MIN_VALUE && moveY != Float.MIN_VALUE) {
                    val xOffset = event.x - moveX
                    val yOffset = event.y - moveY
                    val left = drawRectF.left + xOffset
                    val top = drawRectF.top + yOffset
                    val right = drawRectF.right + xOffset
                    val bottom = drawRectF.bottom + yOffset
                    when {
                        // tl
                        downPositionTag[0] -> {
                            if (refreshDrawRect(top = top, left = left)) {
                                postInvalidate()
                                drawMoved = true
                            }
                        }
                        // tr
                        downPositionTag[1] -> {
                            if (refreshDrawRect(top = top, right = right)) {
                                postInvalidate()
                                drawMoved = true
                            }
                        }
                        // bl
                        downPositionTag[2] -> {
                            if (refreshDrawRect(bottom = bottom, left = left)) {
                                postInvalidate()
                                drawMoved = true
                            }
                        }
                        // br
                        downPositionTag[3] -> {
                            if (refreshDrawRect(bottom = bottom, right = right)) {
                                postInvalidate()
                                drawMoved = true
                            }
                        }
                        else -> {
                            if (left > viewRect.left && top > viewRect.top
                                && right < viewRect.right && bottom < viewRect.bottom
                            ) {
                                drawRectF.left = left
                                drawRectF.top = top
                                drawRectF.right = right
                                drawRectF.bottom = bottom
                                postInvalidate()
                                drawMoved = true
                            }
                        }
                    }
                    moveX = event.x
                    moveY = event.y
                }
            }
            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                if (drawMoved && compareRectF != drawRectF) {
                    oRect.set(drawRectF)
                    onDraggedPosition?.invoke(RectF(drawRectF))
                }
                moveX = Float.MIN_VALUE
                moveY = Float.MIN_VALUE
                postInvalidate()
                downPositionTag.fill(false, 0, downPositionTag.size)
            }
            else -> {

            }
        }
        return true
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        viewRect.right = measuredWidth.toFloat()
        viewRect.bottom = measuredHeight.toFloat()
        if (curRectF != null) {
            val scale = if ((imgSize?.width ?: 0) > 0) viewRect.width() / imgSize!!.width else 1f
            drawRectF.scaleOther(scale, curRectF!!)
            drawRectF.checkSizeAndUpdate(viewRect)
        } else {
            drawRectF.reset()
        }
        oRect.set(viewRect)
    }

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
//        Log.d(TAG, "onLayout: changed: ${changed}, ltrb: $left/$top/$right/$bottom")
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        //Log.d(TAG, "onDraw: ${canvas?.hashCode()}, curRectF: ${drawRectF}")

        canvas ?: return

        if (oRect.hasValue.not()) return
        anim?:return

        if (drawRectF.isEmpty) {
            canvas.drawRoundRect(viewRect, viewRadius, viewRadius, paint)
        } else {

            drawBg(canvas)
            drawCorners(canvas)
        }

    }

    private fun drawBg(canvas: Canvas) {
        clipPath.reset()
        var x = drawRectF.left
        var y = drawRectF.top + cornerRadius
        clipPath.moveTo(x, y)

        x = drawRectF.left + cornerRadius
        y = drawRectF.top
        clipPath.cubicTo(
            drawRectF.left - cubicOffset,
            drawRectF.top - cubicOffset,
            x,
            y,
            x,
            y,
        )

        x = drawRectF.right - cornerRadius
        y = drawRectF.top

        clipPath.lineTo(x, y)

        x = drawRectF.right
        y = drawRectF.top + cornerRadius
        clipPath.cubicTo(
            drawRectF.right + cubicOffset,
            drawRectF.top + cubicOffset,
            x,
            y,
            x,
            y,
        )

        x = drawRectF.right
        y = drawRectF.bottom - cornerRadius
        clipPath.lineTo(x, y)

        x = drawRectF.right - cornerRadius
        y = drawRectF.bottom
        clipPath.cubicTo(
            drawRectF.right + cubicOffset,
            drawRectF.bottom + cubicOffset,
            x,
            y,
            x,
            y,
        )


        x = drawRectF.left + cornerRadius
        y = drawRectF.bottom
        clipPath.lineTo(x, y)

        x = drawRectF.left
        y = drawRectF.bottom - cornerRadius
        clipPath.cubicTo(
            drawRectF.left - cubicOffset,
            drawRectF.bottom - cubicOffset,
            x,
            y,
            x,
            y,
        )
        val saveCount = canvas.save()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//                canvas.clipOutRect(curDrawRectF)
            canvas.clipOutPath(clipPath)
        } else {
//                canvas.clipRect(curDrawRectF, Region.Op.XOR)
            canvas.clipPath(clipPath, Region.Op.XOR)
        }
        canvas.drawRoundRect(viewRect, viewRadius, viewRadius, paint)
        canvas.restoreToCount(saveCount)
    }

    private fun drawCorners(canvas: Canvas) {
        if (cornerRadius * 2 > drawRectF.width() || cornerRadius * 2 > drawRectF.height()) {
            canvas.drawRoundRect(drawRectF, cornerRadius.toFloat(), cornerRadius.toFloat(), borderPaint)
            return
        }

        borderPath.reset()
        borderPath2.reset()

        val lowBordLength = bordLength * 0.5f
        val highBordLength = bordLength * 1.5f
        val bordLengthW =
            (bordLength * (drawRectF.width() * 2 / viewRect.width())).coerceAtMost(highBordLength)
                .coerceAtLeast(lowBordLength).toInt()
        val bordLengthH =
            (bordLength * (drawRectF.height() * 2 / viewRect.height())).coerceAtMost(highBordLength)
                .coerceAtLeast(lowBordLength).toInt()
        val overWidth = bordLengthW * 2 + cornerRadius > drawRectF.width()
        val overHeight =  bordLengthH * 2 + cornerRadius > drawRectF.height()

        val realBordLenW = if (overWidth) { bordLengthW.shr(1) } else bordLengthW
        val realBordLenH = if (overHeight) { bordLengthH.shr(1) } else bordLengthH

        var hBlank = (drawRectF.width() - cornerRadius.shl(1) - realBordLenW.shl(1))
        var vBlank = (drawRectF.height() - cornerRadius.shl(1) - realBordLenH.shl(1))
        hBlank = if (hBlank < 0f) 0f else hBlank
        vBlank = if (vBlank < 0f) 0f else vBlank

        // left-top

        var x = drawRectF.left
        var y = drawRectF.top + realBordLenH + cornerRadius
        
        borderPath.moveTo(x, y)

        x = drawRectF.left
        y -= realBordLenH
        borderPath.lineTo(
            x,
            y
        )

        x = drawRectF.left + cornerRadius
        y = drawRectF.top
        borderPath.cubicTo(
            drawRectF.left - cubicOffset,
            drawRectF.top - cubicOffset,
            x,
            y,
            x,
            y,
        )

        x = drawRectF.left + cornerRadius + realBordLenW
        y = drawRectF.top
        borderPath.lineTo(x, y)
        

        // top & bottom center
        if (hBlank > 0) {
            val whiteLineGapW = centerGapRatio * hBlank
            val grayLineGapW = (hBlank - whiteLineGapW) / 2 - grayLineGap.shl(1)

            // top
            x += grayLineGap
            borderPath2.moveTo(x, y)
            x += grayLineGapW
            borderPath2.lineTo(x, y)

            x += grayLineGap
            borderPath.moveTo(x, y)
            x += whiteLineGapW
            borderPath.lineTo(x, y)
            
            x += grayLineGap
            borderPath2.moveTo(x, y)
            x += grayLineGapW
            borderPath2.lineTo(x, y)
            

            // bottom
            x = drawRectF.left + cornerRadius + realBordLenW
            y = drawRectF.bottom

            x += grayLineGap
            borderPath2.moveTo(x, y)
            x += grayLineGapW
            borderPath2.lineTo(x, y)
            
            x += grayLineGap
            borderPath.moveTo(x, y)
            x += whiteLineGapW
            borderPath.lineTo(x, y)

            x += grayLineGap
            borderPath2.moveTo(x, y)
            x += grayLineGapW
            borderPath2.lineTo(x, y)
        }

        // right--top
        x = drawRectF.right - realBordLenW - cornerRadius
        y = drawRectF.top
        
        borderPath.moveTo(x, y)

        x += realBordLenW
        borderPath.lineTo(x, y)

        x = drawRectF.right
        y = drawRectF.top + cornerRadius
        borderPath.cubicTo(
            drawRectF.right + cubicOffset,
            drawRectF.top + cubicOffset,
            x,
            y,
            x,
            y,
        )

        y += realBordLenH
        borderPath.lineTo(x, y)
        

        // right & left center
        if (vBlank > 0) {
            val whiteLineGapW = centerGapRatio * vBlank
            val grayLineGapW = (vBlank - whiteLineGapW) / 2 - grayLineGap.shl(1)

            // right
            y += grayLineGap
            borderPath2.moveTo(x, y)
            y += grayLineGapW
            borderPath2.lineTo(x, y)
            
            y += grayLineGap
            borderPath.moveTo(x, y)
            y += whiteLineGapW
            borderPath.lineTo(x, y)

            y += grayLineGap
            borderPath2.moveTo(x, y)
            y += grayLineGapW
            borderPath2.lineTo(x, y)
            
            // left
            x = drawRectF.left
            y = drawRectF.top + cornerRadius + realBordLenH

            y += grayLineGap
            borderPath2.moveTo(x, y)
            y += grayLineGapW
            borderPath2.lineTo(x, y)
            
            y += grayLineGap
            borderPath.moveTo(x, y)
            y += whiteLineGapW
            borderPath.lineTo(x, y)
            
            y += grayLineGap
            borderPath2.moveTo(x, y)
            y += grayLineGapW
            borderPath2.lineTo(x, y)
            
        }

        // right--bottom
        x = drawRectF.right
        y = drawRectF.bottom - realBordLenH - cornerRadius
        
        borderPath.moveTo(x, y)

        y += realBordLenH
        borderPath.lineTo(x, y)

        x = drawRectF.right - cornerRadius
        y = drawRectF.bottom
        borderPath.cubicTo(
            drawRectF.right + cubicOffset,
            drawRectF.bottom + cubicOffset,
            x,
            y,
            x,
            y,
        )

        x -= realBordLenW
        borderPath.lineTo(x, y)


        // left-bottom
        x = drawRectF.left + realBordLenW + cornerRadius
        y = drawRectF.bottom

        borderPath.moveTo(x, y)

        x -= realBordLenW
        borderPath.lineTo(x, y)

        x = drawRectF.left
        y = drawRectF.bottom - cornerRadius
        borderPath.cubicTo(
            drawRectF.left - cubicOffset,
            drawRectF.bottom - cubicOffset,
            x,
            y,
            x,
            y,
        )

        y -= realBordLenH
        borderPath.lineTo(x, y)


        canvas.drawPath(borderPath, borderPaint)

        canvas.drawPath(borderPath2, borderPaint2)
    }

    private var anim: ValueAnimator? = null

    private val nRectF = RectF(drawRectF)

    private fun anim2Rect() {
        if (anim?.isRunning == true) {
            return
        }
        if (!oRect.hasValue || !drawRectF.hasValue || oRect == drawRectF) return

        nRectF.set(drawRectF)
        if (anim == null) {
            anim = ValueAnimator.ofFloat(0f, 1F).apply {
                duration = 400L
                interpolator = AccelerateDecelerateInterpolator()
                addUpdateListener {
                    (it.animatedValue as Float?)?.let { value ->
                        drawRectF.left = oRect.left + (nRectF.left - oRect.left) * value
                        drawRectF.top = oRect.top + (nRectF.top - oRect.top) * value
                        drawRectF.right = oRect.right + (nRectF.right - oRect.right) * value
                        drawRectF.bottom = oRect.bottom + (nRectF.bottom - oRect.bottom) * value
                        postInvalidate()
                    }
                }
                addListener(onEnd = {
                    oRect.set(nRectF)
                })
            }
        } else {
            anim?.duration = 200L
        }
        anim?.start()
    }

    companion object {

        private const val TAG = "ThingFrameView"
    }
}